﻿using Autofac;
using Autofac.Core;
using AutoRegistDependency.Attributes;
using AutoRegistDependency.Component;
using Castle.DynamicProxy;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
namespace AutoRegistDependency.Implement
{
    /// <summary>
    /// 聚合接口代理类
    /// 大部分代码来自 Autofac.Extra.AggregateService 做了部分修改 支持特性解析服务
    /// </summary>
    public class AggregateServiceInterceptor : IInterceptor
    {
        private static readonly Assembly SystemAssembly = typeof(Func<>).Assembly;
        private readonly IComponentContext _context;
        private Dictionary<MethodInfo, Action<IInvocation>> _invocationMap;
        private static object locker = new object();
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="context">解析上下文</param>
        /// <param name="interfaceRealType">接口真实类型(如果是泛型接口此处为封闭式泛型)</param>
        /// <param name="registInfo">组件注册信息 为了获取Autowied字段和方法的信息</param>
        public AggregateServiceInterceptor(IComponentContext context, Type interfaceRealType, ComponentDefinition registInfo)
        {
            _context = context;
            SetupInvocationMap(interfaceRealType, registInfo);
        }
        /// <summary>
        /// 执行代理方法
        /// </summary>
        /// <param name="invocation"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public void Intercept(IInvocation invocation)
        {
            if (invocation == null)
            {
                throw new ArgumentNullException(nameof(invocation));
            }

            // Generic methods need to use the open generic method definition.
            var method = invocation.Method.IsGenericMethod ? invocation.Method.GetGenericMethodDefinition() : invocation.Method;
            var invocationHandler = _invocationMap[method];
            invocationHandler(invocation);
        }
        private void SetupInvocationMap(Type interfaceRealType, ComponentDefinition registInfo)
        {
            if (_invocationMap == null)
            {
                lock (locker)
                {
                    if (_invocationMap == null)
                    {
                        var types = new HashSet<Type>();
                        foreach (var interfaceType in interfaceRealType.GetInterfaces())
                        {
                            if (types.Contains(interfaceType))
                            {
                                continue;
                            }

                            types.Add(interfaceType);
                        }

                        if (interfaceRealType.IsInterface && !types.Contains(interfaceRealType))
                        {
                            types.Add(interfaceRealType);
                        }
                        var methods = types
                            .SelectMany(x => x.GetMethods())
                            .ToArray();

                        var methodMap = new Dictionary<MethodInfo, Action<IInvocation>>(methods.Length);
                        foreach (var method in methods)
                        {
                            var returnType = method.ReturnType;
                            AutowiredAttribute autowired = method.GetCustomAttribute<AutowiredAttribute>();
                            if (returnType == typeof(void))
                            {
                                // Any method with 'void' return type (includes property setters) should throw an exception
                                methodMap.Add(method, InvalidReturnTypeInvocation);
                                continue;
                            }
                            PropertyInfo prop = GetProperty(method);
                            if (prop != null)
                            {
                                object propertyValue;
                                // if property has AutowiredAttribute
                                if (registInfo.AutowiredProperties.Contains(prop))
                                {
                                    autowired = prop.GetCustomAttribute<AutowiredAttribute>();
                                    propertyValue = autowired.GetServiceInstance(prop.PropertyType, new ResolveParameter(), _context);
                                }
                                else
                                {
                                    // All properties should be resolved at proxy instantiation
                                    propertyValue = _context.Resolve(returnType);
                                }
                                methodMap.Add(method, invocation => invocation.ReturnValue = propertyValue);
                                continue;
                            }

                            // For methods with parameters, cache parameter info for use at invocation time
                            var parameters = method.GetParameters()
                                .OrderBy(parameterInfo => parameterInfo.Position)
                                .Select(parameterInfo => new { parameterInfo.Position, parameterInfo.ParameterType })
                                .ToArray();

                            if (parameters.Length > 0)
                            {
                                // Methods with parameters
                                if (parameters.Any(p => p.ParameterType.IsGenericType))
                                {
                                    // There are some open generic parameters so resolve a
                                    // Func<X, Y> corresponding to the method parameters and
                                    // method return type. Core Autofac will handle the type
                                    // mapping from open generic to closed generic, etc.
                                    methodMap.Add(
                                        method,
                                        invocation =>
                                        {
                                            var targetMethod = invocation.Method;
                                            var funcArgTypes = targetMethod.GetParameters().OrderBy(p => p.Position).Select(p => p.ParameterType).Append(targetMethod.ReturnType).ToArray();
                                            var funcTypeName = $"System.Func`{funcArgTypes.Length}";
                                            var baseFuncType = SystemAssembly.GetType(funcTypeName);
                                            if (baseFuncType == null)
                                            {
                                                throw new NotSupportedException($"Unable to locate function type for dynamic resolution: {funcTypeName}. Ensure your method doesn't have too many parameters to convert to a System.Func delegate.");
                                            }
                                            var builtFuncType = baseFuncType.MakeGenericType(funcArgTypes);
                                            var factory = (Delegate)_context.Resolve(builtFuncType);
                                            invocation.ReturnValue = factory.DynamicInvoke(invocation.Arguments);
                                        });
                                }
                                else
                                {
                                    // There are no open generic parameters so we can simplify the backing method.
                                    methodMap.Add(
                                        method,
                                        invocation =>
                                        {
                                            var arguments = invocation.Arguments;
                                            var typedParameters = parameters
                                                .Select(info => (Parameter)new TypedParameter(info.ParameterType, arguments[info.Position]));

                                            if (autowired != null)
                                            {
                                                Service serviceKey = autowired.GetResolveService(invocation.Method.ReturnType);
                                                if (_context.TryResolveService(serviceKey, typedParameters, out object instance))
                                                {
                                                    invocation.ReturnValue = instance;
                                                }
                                            }
                                            else
                                            {
                                                // To handle open generics, this resolves the return type of the invocation rather than the scanned method.
                                                invocation.ReturnValue = _context.Resolve(invocation.Method.ReturnType, typedParameters);
                                            }

                                        });
                                }

                                continue;
                            }

                            // Methods without parameters
                            //var methodWithoutParams = GetType().GetMethod("MethodWithoutParams", BindingFlags.Instance | BindingFlags.NonPublic);
                            //var methodWithoutParamsDelegate = (Action<IInvocation>)methodWithoutParams.CreateDelegate(typeof(Action<IInvocation>), this);
                            methodMap.Add(method, invocation =>
                            {
                                if (autowired != null)
                                {
                                    invocation.ReturnValue = autowired.GetServiceInstance(method.ReturnType, new ResolveParameter(), _context);
                                }
                                else
                                {
                                    invocation.ReturnValue = _context.Resolve(method.ReturnType);
                                }
                            });
                        }
                        _invocationMap = methodMap;
                    }
                }
            }

        }

        private static PropertyInfo GetProperty(MethodInfo method)
        {
            var takesArg = method.GetParameters().Length == 1;
            var hasReturn = method.ReturnType != typeof(void);

            if (takesArg == hasReturn)
            {
                return null;
            }

            return method
                .DeclaringType
                .GetProperties()
                .Where(prop => prop.GetGetMethod() == method)
                .FirstOrDefault();
        }
        private static void InvalidReturnTypeInvocation(IInvocation invocation)
        {
            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "The method {0} has invalid return type System.Void", invocation.Method));
        }

    }
}
